/*
 * Copyright © OpenAtom Foundation.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package io.iec.edp.caf.databaseobject.common;

import io.iec.edp.caf.databaseobject.api.configuration.ObjectNameMaxLengthConfiguration;
import io.iec.edp.caf.databaseobject.api.context.DatabaseObjectReservedWords;
import io.iec.edp.caf.databaseobject.api.context.DatabaseReservedWords;
import io.iec.edp.caf.databaseobject.api.entity.*;
import io.iec.edp.caf.databaseobject.common.helper.ObjectMaxLengthHelper;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @author liu_wei
 */
public class DatabaseObjectCheckUtil {
    public void checkDatabaseObjectAvailable(AbstractDatabaseObject databaseObject) {
        checkForNullReference(databaseObject, "databaseObject");
        checkForEmptyString(databaseObject.getCode(), "databaseObjectCode");

        this.checkObjectCode(databaseObject.getCode());
        if (databaseObject.getType() == DatabaseObjectType.Table || databaseObject.getType() == DatabaseObjectType.TempTable) {
            DatabaseObjectTable table = (DatabaseObjectTable) databaseObject;
            checkDatabaseTableColumns(table);
            checkDatabaseObjectIndex(table);
            checkPrimaryKey(table);
            checkDBOCodeLength(table);

        } else if (databaseObject.getType() == DatabaseObjectType.View) {
            checkForNullReference(((DatabaseObjectView) databaseObject).getDefination(), "databaseObjectDefinition");
        } else {
            throw new RuntimeException("数据库对象类型不正确，请确认。");
        }

    }

    public void checkDboFilePostfix(String dboFileName) {
        if (!dboFileName.endsWith(".dbo")) {
            throw new RuntimeException("该文件名后缀不正确");
        }
    }

    private void checkPrimaryKey(DatabaseObjectTable databaseObject) {
        if (databaseObject.getPrimaryKey() != null && databaseObject.getPrimaryKey().size() > 0) {
            if (databaseObject.getPrimaryKey().size() == 1) {

                DatabaseObjectColumn column = (DatabaseObjectColumn) databaseObject.getColumnById(databaseObject.getPrimaryKey().get(0));
                if (column == null) {
                    throw new RuntimeException("当前字段不存在，字段ID为" + databaseObject.getPrimaryKey().get(0));
                }
                if (column.isNullable()) {
                    throw new RuntimeException("当前主键字段是否可为空属性不能为true");
                }
                if (!column.isUnique()) {
                    throw new RuntimeException("当前主键字段是否唯一属性不能为false");
                }
                if (databaseObject.getMultiLanguageColumns() != null && databaseObject.getMultiLanguageColumns().size() > 0 && databaseObject.getMultiLanguageColumns().contains(databaseObject.getPrimaryKey().get(0))) {
                    throw new RuntimeException("多语字段不许设置成主键");
                }
            } else {
                for (String primaryKey : databaseObject.getPrimaryKey()) {
                    DatabaseObjectColumn column = (DatabaseObjectColumn) databaseObject.getColumnById(primaryKey);
                    if (column == null) {
                        throw new RuntimeException(String.format("当前字段不存在，字段ID为%1$s", primaryKey));
                    }
                    if (column.isNullable()) {
                        throw new RuntimeException("当前主键字段是否可为空属性不能为true");
                    }
                    if (databaseObject.getMultiLanguageColumns() != null && databaseObject.getMultiLanguageColumns().size() > 0 && databaseObject.getMultiLanguageColumns().contains(databaseObject.getPrimaryKey().get(0))) {
                        throw new RuntimeException("多语字段不许设置成主键");
                    }
                    if (column.getDataType() == DataType.Clob || column.getDataType() == DataType.Blob || column.getDataType() == DataType.NClob) {
                        throw new RuntimeException(String.format("当前字段类型不支持设置为主键，请确认。当前字段编号为：%1$s，字段类型为%2$s", column.getCode(), column.getDataType().toString()));
                    }
                }
            }
        } else {
            DatabaseObjectColumn column = (DatabaseObjectColumn) databaseObject.getColumnByCode("ID");
            if (column == null) {
                throw new RuntimeException("数据库对象表必须包含主键或ID字段，请确认。");
            }
            if (column.isNullable()) {
                throw new RuntimeException("数据库对象表未设置主键时，ID字段必须是非空的");
            }
        }
    }


    private void checkDatabaseTableColumns(DatabaseObjectTable databaseObject) {
        checkForNullReference(databaseObject, "databaseObject");
        checkForEmptyString(databaseObject.getCode(), "databaseObjectCode");
        checkForNullReference(databaseObject.getColumns(), "databaseObjectColumns");
        if (databaseObject.getColumns().size() <= 0) {
            throw new RuntimeException("数据库对象字段列表不能为空。");
        }

        for (DatabaseObjectColumn column : databaseObject.getColumns()) {
            if ((column.getDataType() == DataType.Clob || column.getDataType() == DataType.Blob || column.getDataType() == DataType.NClob) && column.isUnique()) {
                throw new RuntimeException(String.format("当前字段类型不支持设置唯一约束，请确认。当前字段编号为：%1$s，字段类型为%2$s", column.getCode(), column.getDataType().toString()));
            }
            if (column.isUnique() && column.isNullable()) {
                throw new RuntimeException(String.format("字段为唯一字段，要求字段为非空字段，请了解。字段编号为%1$s", column.getCode()));
            }
            if (column.getDataType() == DataType.NotSupported) {
                throw new RuntimeException(databaseObject.getCode() + "中" + column.getCode() + "字段数据类型不支持，请修改。");
            }
        }
        checkColumnRepeat(databaseObject.getColumns());
        checkColumnCode(databaseObject);
        checkColumnDataType(databaseObject);
        checkPrimaryKey(databaseObject);
    }


    private void checkColumnCode(DatabaseObjectTable databaseObject) {
        for (DatabaseObjectColumn column : databaseObject.getColumns()) {
            checkObjectCode(column.getCode());

        }
        if (databaseObject.isUsingTimeStamp()) {
            for (DatabaseObjectColumn column : databaseObject.getColumns()) {
                checkDatabaseObjectReservedWords(column.getCode());
            }
        }
    }

    private static void checkDatabaseObjectReservedWords(String code) {
        if (DatabaseObjectReservedWords.isReservedWord(code)) {
            StringBuilder str = new StringBuilder();
            for (String reservedWord : DatabaseObjectReservedWords.getReservedWords()) {
                str.append("[").append(reservedWord).append("]");
            }
            str.append("是数据库对象保留字，字段编号不能是数据库对象保留字，请确认。当前编号：").append(code);
            throw new RuntimeException(str.toString());
        }
    }

    public final void checkObjectCode(String code) {
        String pattern = "^[a-zA-Z_\u007f-\u00ff][a-zA-Z0-9_\u007f-\u00ff]*$";

        boolean isMatch = Pattern.matches(pattern, code);
        if (!isMatch) {
            throw new RuntimeException("编号/对象名命名不规范:\n只能包含字母,数字,下划线,并且不可以用数字开头!");
        }
        if (DatabaseReservedWords.isReservedWord(code)) {
            throw new RuntimeException("编号不能是数据库保留字，请确认。当前编号：" + code);
        }
    }

    private void checkColumnRepeat(List<DatabaseObjectColumn> columns) {
        HashMap<String, String> dictionary = new HashMap<>();
        for (DatabaseObjectColumn column : columns) {
            if (dictionary.containsKey(column.getId())) {
                throw new RuntimeException("数据库对象存在ID重复的字段,ID为" + column.getId());
            } else {
                if (dictionary.containsValue(column.getCode().toLowerCase())) {
                    throw new RuntimeException("数据库对象存在编号重复的字段,编号为" + column.getCode());
                }
            }
            dictionary.put(column.getId(), column.getCode().toLowerCase());
        }

    }

    private static DataType convertToI18NDataType(DataType dataType, String columnCode) {
        if (dataType == DataType.Varchar || dataType == DataType.NVarchar) {
            return DataType.NVarchar;
        }
        if (dataType == DataType.Char || dataType == DataType.NChar) {
            return DataType.NChar;
        }
        if (dataType == DataType.Clob || dataType == DataType.NClob) {
            return DataType.NClob;
        } else {
            throw new RuntimeException("当前字段数据类型不支持多语，字段编号为" + columnCode + "，当前数据类型为" + dataType + "，请确认。");
        }
    }

    private static void checkColumnDataType(DatabaseObjectTable databaseObject) {
        for (DatabaseObjectColumn column : databaseObject.getColumns()) {
            if (databaseObject.getMultiLanguageColumns().contains(column.getId())) {
                column.setDataType(convertToI18NDataType(column.getDataType(), column.getCode()));
            }
            if ((column.getDataType() == DataType.Varchar || column.getDataType() == DataType.Char) && column.getLength() > 8000) {
                column.setLength(8000);
                column.setPrecision(0);
                column.setScale(0);
            } else if ((column.getDataType() == DataType.NVarchar || column.getDataType() == DataType.NChar) && column.getLength() > 4000) {
                column.setLength(4000);
                column.setPrecision(0);
                column.setScale(0);
            } else if ((column.getDataType() == DataType.Varchar || column.getDataType() == DataType.Char || column.getDataType() == DataType.NVarchar || column.getDataType() == DataType.NChar) && column.getLength() <= 0) {
                column.setLength(36);
                column.setPrecision(0);
                column.setScale(0);
            } else if (column.getDataType() == DataType.Decimal) {
                if (column.getPrecision() > 38) {
                    column.setPrecision(38);
                }

                if (column.getScale() > column.getPrecision()) {
                    column.setScale(column.getPrecision());
                }
                if (column.getPrecision() <= 0) {
                    //默认18？
                    column.setPrecision(18);
                }
                if (column.getScale() < 0) {
                    column.setScale(0);
                }
                column.setLength(0);

            } else if (column.getDataType() == DataType.Int || column.getDataType() == DataType.Clob || column.getDataType() == DataType.NClob || column.getDataType() == DataType.Blob || column.getDataType() == DataType.DateTime || column.getDataType() == DataType.TimeStamp) {
                column.setLength(0);
                column.setPrecision(0);
                column.setScale(0);
            }
        }
    }

    private void checkDatabaseObjectIndex(DatabaseObjectTable databaseObject) {
        if (databaseObject.getIndexes() == null || databaseObject.getIndexes().size() <= 0) {
            return;
        }
        checkIndexCode(databaseObject);
        long indexesCount = databaseObject.getIndexes().stream().filter((item) -> item.isCluster_Constraint()).count();
        if (indexesCount > 1) {
            throw new RuntimeException("数据库表只能包含一个聚集索引，请确认。");
        }

        checkIndexRepeat(databaseObject);
        checkIndexColumns(databaseObject);
    }

    private void checkIndexCode(DatabaseObjectTable databaseObject) {
        for (DatabaseObjectIndex index : databaseObject.getIndexes()) {
            checkObjectCode(index.getCode());
        }
    }

    private void checkIndexColumns(DatabaseObjectTable databaseObject) {
        boolean isExistPrimaryKey = databaseObject.getPrimaryKey() != null && databaseObject.getPrimaryKey().size() > 0;
        for (DatabaseObjectIndex index : databaseObject.getIndexes()) {
            if (index.getColumns() == null || index.getColumns().size() <= 0) {
                throw new RuntimeException(String.format("索引关联的字段不能为空，索引编号为%1$s", index.getCode()));
            }
            checkColumnRepeat(index.getColumns());
            for (DatabaseObjectColumn column : index.getColumns()) {
                if (column.getDataType() == DataType.Clob || column.getDataType() == DataType.Blob || column.getDataType() == DataType.NClob) {
                    throw new RuntimeException("当前字段类型不支持设置索引，请确认。当前字段编号为：" + column.getCode() + "，字段类型为" + column.getDataType().toString());
                }
            }
            boolean isExsitesIndex = true;
            if (index.getColumns().size() == 1) {
                if ((index.getColumns().get(0).isUnique() && databaseObject.getPrimaryKey().size() == 1) || (index.getColumns().get(0).isUnique() && databaseObject.getPrimaryKey().size() > 1 && !databaseObject.getPrimaryKey().contains(index.getColumns().get(0).getId()))) {
                    throw new RuntimeException("编号为" + index.getColumns().get(0).getCode() + "的字段已设置非空约束/主键，无法再添加索引，请确认。");
                }
            }
            if (isExistPrimaryKey && index.getColumns().size() == databaseObject.getPrimaryKey().size()) {

                for (DatabaseObjectColumn column : index.getColumns()) {
                    if (!databaseObject.getPrimaryKey().contains(column.getId())) {
                        isExsitesIndex = false;
                    }
                }
            } else {
                isExsitesIndex = false;
            }
            if (isExsitesIndex) {
                throw new RuntimeException("编号为" + index.getCode() + "的索引关联字段与主键关联字段相同，无法创建该索引，请确认。");
            }
        }
    }

    private static void checkIndexRepeat(DatabaseObjectTable databaseObject) {
        HashMap<String, String> dictionary = new HashMap<>();
        for (DatabaseObjectIndex index : databaseObject.getIndexes()) {
            if (dictionary.containsKey(index.getId())) {
                throw new RuntimeException("数据库对象存在ID重复的索引,ID为" + index.getId());
            } else {
                if (dictionary.containsValue(index.getCode().toLowerCase())) {
                    throw new RuntimeException("数据库对象存在编号重复的索引,编号为" + index.getCode());
                }
            }
            dictionary.put(index.getId(), index.getCode().toLowerCase());
        }

    }

    public final void checkDBOCodeLength(DatabaseObjectTable table) {
        ObjectNameMaxLengthConfiguration maxLengthConfig = null;
        try {
            maxLengthConfig = ObjectMaxLengthHelper.getInstance().GetObjectNameMaxLengthConfiguration();
        } catch (IOException e) {
            throw new RuntimeException("获取数据库对象名称最大长度配置报错。", e);
        }
        if (table.getCode().length() > maxLengthConfig.getTableNameMaxLength()) {
            throw new RuntimeException(String.format("数据库对象表编号超长，编号为：%1$s", table.getCode()));
        }

        for (DatabaseObjectColumn column : table.getColumns()) {
            if (column.getCode().length() > maxLengthConfig.getColumnNameMaxLength()) {
                throw new RuntimeException("数据库对象表字段编号超长，字段编号为：" + column.getCode() + "，数据库对象编号为" + table.getCode());
            }
            if (column.isUnique() && (table.getPrimaryKey() == null || (table.getPrimaryKey() != null && table.getPrimaryKey().size() <= 0) || (table.getPrimaryKey() != null && table.getPrimaryKey().size() > 0 && !table.getPrimaryKey().contains(column.getId())))) {
                String uqConstraintName = GetColumnUCName(column, table.getCode());
                if (uqConstraintName.length() > maxLengthConfig.getConstraintNameMaxLength()) {
                    throw new RuntimeException("约束名称超长，约束名称为：" + uqConstraintName + "，数据库对象编号为" + table.getCode());
                }
            }
        }
        if (table.getIndexes() != null && table.getIndexes().size() > 0) {

            for (DatabaseObjectIndex index : table.getIndexes()) {
                if (index.getCode().length() > maxLengthConfig.getIndexNameMaxLength()) {
                    throw new RuntimeException("数据库对象表索引编号超长，索引编号为：" + index.getCode() + "，数据库对象编号为" + table.getCode());
                }
            }
        }
        String pkName = "";
        if (table.getPkName() != null && table.getPkName().length() > 0) {
            pkName = table.getPkName();
        } else {
            pkName = getPkName(table.getCode());
        }

        if (pkName.length() > maxLengthConfig.getPkNameMaxLength()) {
            throw new RuntimeException("主键名称超长，主键名称为：" + pkName + "，数据库对象编号为" + table.getCode());
        }
    }

    public final String GetColumnUCName(DatabaseObjectColumn column, String tableCode) {
        String uqConstraintName;
        if (column.getUcName() != null && column.getUcName().length() > 0) {
            uqConstraintName = column.getUcName();
        } else {
            uqConstraintName = "UQ_" + tableCode + "_" + column.getCode();
        }
        return uqConstraintName;
    }

    public final String getPkName(String tableCode) {
        return "PK_" + tableCode;
    }

    private void checkForEmptyString(String strValue, String strName) throws RuntimeException {
        if (strValue == null || strValue.length() <= 0) {
            throw new RuntimeException(strName + "不能为空");
        }
    }

    private void checkForNullReference(Object objectValue, String objectName) throws RuntimeException {
        if (objectValue == null) {
            throw new RuntimeException(objectName + "不能为null");
        }
    }
}
